/*****************************************************************************

  The following code is derived, directly or indirectly, from the SystemC
  source code Copyright (c) 1996-2006 by all Contributors.
  All Rights reserved.

  The contents of this file are subject to the restrictions and limitations
  set forth in the SystemC Open Source License Version 2.4 (the "License");
  You may not use this file except in compliance with such restrictions and
  limitations. You may obtain instructions on how to receive a copy of the
  License at http://www.systemc.org/. Software distributed by Contributors
  under the License is distributed on an "AS IS" basis, WITHOUT WARRANTY OF
  ANY KIND, either express or implied. See the License for the specific
  language governing rights and limitations under the License.

 *****************************************************************************/

/*****************************************************************************

  switch.cpp - This is the implementation file for the asynchronous process
               "switch".

  Original Author: Rashmi Goswami, Synopsys, Inc.

 *****************************************************************************/

/*****************************************************************************

  MODIFICATION LOG - modifiers, enter your name, affiliation, date and
  changes you are making here.

      Name, Affiliation, Date:
  Description of Modification:

 *****************************************************************************/

#include "systemc.h"
#include "pkt.h"
//#include "fifo.h"
#include "switch.h"
#include "time.h"
#include <queue>   // for std::queue

#define SIM_NUM 50000
#define QUEUE_SIZE 1024
#define OUTPUT_QUEUE
#undef LOG_QUEUE_LENGTH
void pkt_switch :: entry()
{
//    pkt_port temp_pkt_port;
    pkt temp_pkt;
    int sim_count=0;
    int pkt_count=0;
    int drop_count[PORTNUM];
//    int pkt_sum;
//    int drop_sum;
    int i, j, k;

    pkt SWsend_pkt[PORTNUM], SWreceive_pkt[PORTNUM], SWprocess_pkt[PORTNUM];
    csix_l2_hdr *SW_l2_hdr[PORTNUM];
//    int temp_outport;
    unsigned int SWreceive_bytes[PORTNUM], SWsend_bytes[PORTNUM];
    unsigned char  SWRX_data[PORTNUM];
    sc_uint<8> SWTX_data[PORTNUM];
    bool SWSOF[PORTNUM];
    bool SWsend_ready[PORTNUM];
    std::queue<pkt> q_in[PORTNUM];
    std::queue<pkt> q_out[PORTNUM];

#ifdef INPUT_QUEUE
    int output_ptr[PORTNUM];
    int output_input_map[PORTNUM][PORTNUM];
#endif

#ifdef LOG_QUEUE_LENGTH
    std::ofstream *fout;
    char filename[PORTNUM][16];

    for(i = 0; i < PORTNUM; i++)
    {
        sprintf(filename[i], "QUEUE%d.csv", i);
    }

#endif

    // initialization
    sim_count  = 0;

    for(i = 0; i < PORTNUM; i++)
    {
        SWreceive_bytes[i] = 0;
        SWsend_bytes[i] = 0;
//    set ready bits
        SWsend_ready[i] = true;
        SWTXClk[i].write(0);
        SWTXSOF[i].write(0);
    }

    srand((unsigned)time(NULL));
    // result = fopen("result","w");

    cout << endl;
    cout << "-------------------------------------------------------------------------------" << endl;
    cout << endl << "             " << PORTNUM << "-port Ethernet Switch Simulation" << endl;
    cout << "-------------------------------------------------------------------------------" << endl;
    cout << "  This is the simulation of a " << PORTNUM << "-port ethernet switch.  " << endl;
    cout << "  The switch uses a input and output queue with both " << PORTNUM << " slots     " << endl;

    // functionality
    while(true)
    {
        wait();

        for(i = 0; i < PORTNUM; i++)
        {
            if((bool)SWRXClk[i] && SWRXClk[i].event())
            {
                SWRX_data[i] = (unsigned char)SWRX[i].read();
                SWSOF[i] = SWRXSOF[i].read();

                if(SWSOF[i] && SWreceive_bytes[i] > 2)
                {
                    SWreceive_pkt[i].pkt_length = SWreceive_bytes[i];
                    SWreceive_bytes[i] = 0;

                    //write packet processing code here
                    //push packet into input queue
                    if(q_in[i].size() > QUEUE_SIZE)
                    { drop_count[i]++; }
                    else
                    {
#ifdef INPUT_QUEUE
                        q_in[i].push(SWreceive_pkt[i]);
#endif
#ifdef OUTPUT_QUEUE
                    SW_l2_hdr[i] = (csix_l2_hdr *)(SWreceive_pkt[i].data + sizeof(csix_base_hdr));
                    j = SW_l2_hdr[i]->dst_port;
                    q_in[j].push(SWreceive_pkt[i]);
#endif
                    }
                }

                SWreceive_pkt[i].data[SWreceive_bytes[i]] = SWRX_data[i];
                SWreceive_bytes[i] += 1;
            }
        }

        if(InSWTXClk.event())
        {
            if((bool)InSWTXClk)
            {
                for(i = 0; i < PORTNUM; i++)
                {
                    if(SWsend_ready[i] && !q_out[i].empty())
                    {
                        SWsend_pkt[i] = q_out[i].front();
                        q_out[i].pop();
                        SWsend_ready[i] = false;
                        SWsend_bytes[i] = 0;
                    }

                    if(!SWsend_ready[i] && SWsend_bytes[i] < SWsend_pkt[i].pkt_length)
                    {

                        SWTXClk[i].write(InSWTXClk);

                        if((bool)InSWTXClk)
                        {
                            SWTX_data[i] = (sc_uint<8>)(SWsend_pkt[i].data[SWsend_bytes[i]]);
                            SWTX[i].write(SWTX_data[i]);
                            SWTXSOF[i].write(!SWsend_bytes[i]);
                            SWsend_bytes[i] += 1;
                        }
                    }
                    else if(!SWsend_ready[i] && SWsend_bytes[i] >= SWsend_pkt[i].pkt_length)//send finished
                    {
                        //cout << name() << " sent one packet to inport" << i << ", length=" << SWsend_bytes[i] << endl;
                        SWsend_bytes[i] = 0;
                        SWsend_ready[i] = true;
                    }
                }
            }
            else
            {
                for(i = 0; i < PORTNUM; i++)
                {
                    SWTXClk[i].write(InSWTXClk);
                }
            }
        }

        //Processing
        if((bool)switch_cntrl && switch_cntrl.event())
        {
#ifdef INPUT_QUEUE
            sim_count++;
            /////look at the dest of first packet in the queue, build the map table////
            for(i = 0; i < PORTNUM; i++)
            {
                output_ptr[i] = 0;
            }

            for(i = 0; i < PORTNUM; i++)
            {
                if(!q_in[i].empty())
                {
                    SWprocess_pkt[i] = q_in[i].front();
                    SW_l2_hdr[i] = (csix_l2_hdr *)(SWprocess_pkt[i].data + sizeof(csix_base_hdr));
                    j = SW_l2_hdr[i]->dst_port;
                    output_input_map[j][output_ptr[j]] = i;
                    output_ptr[j]++;
                }
            }

            /////randomly select an entry in the map table to output////
            for(i = 0; i < PORTNUM; i++)
            {
                if(output_ptr[i] != 0)
                {
                    k = output_input_map[i][rand() % output_ptr[i]];
                    q_out[i].push(q_in[k].front());
                    q_in[k].pop();
                    pkt_count+=1;
                    if (pkt_count%1000==0){
cout<<"Switch processes "<<pkt_count<<" packets in "<<sim_count<<" cycles."<<endl;
                    }
                }
            }
#endif
#ifdef OUTPUT_QUEUE
        sim_count++;

        /////output packets////
        for(i = 0; i < PORTNUM; i++)
        {
            if(!q_in[i].empty())
            {
                q_out[i].push(q_in[i].front());
                q_in[i].pop();
                    pkt_count+=1;
                    if (pkt_count%1000==0){
cout<<"Switch processes "<<pkt_count<<" packets in "<<sim_count<<" cycles."<<endl;
                    }
            }
        }
#endif
        }
}

sc_stop();

/*
cout << endl << endl << "-------------------------------------------------------------------------------" << endl;
cout << "End of switch operation at " << sc_time_stamp() << endl;
cout << "Total number of packets received: ";
pkt_sum = 0;
drop_sum = 0;

for(i = 0; i < PORTNUM; i++)
{
    cout << pkt_count[i] << "+";
    pkt_sum += pkt_count[i];
}

cout << "=" << pkt_sum << endl;
cout << "Total number of packets dropped: " ;

for(i = 0; i < PORTNUM; i++)
{
    cout << drop_count[i] << "+";
    drop_sum += drop_count[i];
}

cout << "=" << drop_sum << endl;
//cout << "Performance achieved:  " <<  float((pkt_sum-drop_sum)*100)/float(sim_count*PORTNUM)<< "%"<< endl;
cout << "-------------------------------------------------------------------------------" << endl;
*/
}
